package com.seventeen.monster.redisson.annotation;

import com.seventeen.monster.redisson.autoconfigure.ListenerContainerConfiguration;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.OrderComparator;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.AnnotatedElement;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * @author seventeen
 */
public class DelayedQueueListenerBeanPostProcessor implements ApplicationContextAware,
        BeanPostProcessor,
        InitializingBean,
        SmartLifecycle {

    private boolean running = false;
    private ApplicationContext applicationContext;
    private AnnotationEnhancer enhancer;
    private ListenerContainerConfiguration listenerContainerConfiguration;

    public DelayedQueueListenerBeanPostProcessor(ListenerContainerConfiguration listenerContainerConfiguration) {
        this.listenerContainerConfiguration = listenerContainerConfiguration;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * @param bean     spring bean
     * @param beanName spring bean name
     * @return spring bean
     * @throws BeansException
     * @description: 对注解进行增强
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        DelayedQueueListener ann = targetClass.getAnnotation(DelayedQueueListener.class);

        if (ann != null) {
            DelayedQueueListener enhance = enhance(targetClass, ann);
            if (listenerContainerConfiguration != null) {
                listenerContainerConfiguration.registerContainer(beanName, bean, enhance);
            }
        }
        return bean;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        buildEnhancer();
        this.listenerContainerConfiguration = this.applicationContext.getBean(ListenerContainerConfiguration.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void start() {
        if (!isRunning()) {
            this.setRunning(true);
            listenerContainerConfiguration.startContainer();
        }
    }

    @Override
    public void stop() {
        if (isRunning()) {
            this.setRunning(false);
            listenerContainerConfiguration.stopContainer();
        }
    }

    @Override
    public int getPhase() {
        return Integer.MAX_VALUE - 2000;
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    private void buildEnhancer() {
        if (this.applicationContext != null) {
            Map<String, AnnotationEnhancer> enhancersMap =
                    this.applicationContext.getBeansOfType(AnnotationEnhancer.class, false, false);
            if (!enhancersMap.isEmpty()) {
                List<AnnotationEnhancer> enhancers = enhancersMap.values()
                        .stream()
                        .sorted(new OrderComparator())
                        .collect(Collectors.toList());
                this.enhancer = (attrs, element) -> {
                    Map<String, Object> newAttrs = attrs;
                    for (AnnotationEnhancer enh : enhancers) {
                        newAttrs = enh.apply(newAttrs, element);
                    }
                    return attrs;
                };
            }
        }
    }

    private DelayedQueueListener enhance(AnnotatedElement element, DelayedQueueListener ann) {
        if (this.enhancer == null) {
            return ann;
        } else {
            return AnnotationUtils.synthesizeAnnotation(
                    this.enhancer.apply(AnnotationUtils.getAnnotationAttributes(ann), element), DelayedQueueListener.class, null);
        }
    }

    public interface AnnotationEnhancer extends BiFunction<Map<String, Object>, AnnotatedElement, Map<String, Object>> {
    }
}
